#include "stdafx.h"
#include "NetKVMnetsh.h"
#include "RegAccess.h"
#include "NetKVMAux.h"
#include "RegParam.h"

// This is NetSH Helper GUID {D9C599C4-8DCF-4a6a-93AA-A16FE6D5125C}
static const GUID NETKVM_HELPER_GUID = {0xd9c599c4, 0x8dcf, 0x4a6a, {0x93, 0xaa, 0xa1, 0x6f, 0xe6, 0xd5, 0x12, 0x5c}};
static const DWORD NETKVM_HELPER_VERSION = 1;

static const LPCTSTR NETKVM_HELPER_NAME = TEXT("NetKVM");
static const LPWSTR NETKVM_HELPER_NAME_W = L"NetKVM";

static const LPCTSTR NETKVM_DEVICES_CLASS = TEXT("net");

// TODO: put real device numbers here
static const LPCTSTR NETKVM_DEVICE_VENDOR_ID = TEXT("");
static const LPCTSTR NETKVM_DEVICE_HARDWARE_ID = TEXT("");

static const LPCWSTR NETKVM_IDX_PARAM_NAME = L"idx";
static const LPCTSTR NETKVM_IDX_PARAM_NAME_T = TEXT("idx");
static const LPCWSTR NETKVM_PARAM_PARAM_NAME = L"param";
static const LPCTSTR NETKVM_PARAM_PARAM_NAME_T = TEXT("param");
static const LPCWSTR NETKVM_VALUE_PARAM_NAME = L"value";
static const LPCTSTR NETKVM_VALUE_PARAM_NAME_T = TEXT("value");

static HINSTANCE g_hinstThisDLL = NULL;

static bool _NetKVMGetDeviceClassGuids(vector<GUID> &GUIDs)
{
    GUID *pguidDevClassPtr = NULL;
    DWORD dwNumGuids;

    if (!SetupDiClassGuidsFromNameEx(NETKVM_DEVICES_CLASS, NULL, 0, &dwNumGuids, NULL, NULL))
    {
        DWORD dwErr = GetLastError();

        if (ERROR_INSUFFICIENT_BUFFER == dwErr)
        {
            /* On first attempt we pass NULL and 0 as GUID array and GUID array size respectively;
            according to SetupDiClassGuidsFromNameEx, the function sets RequiredSize output formal parameter
            (&dwNumGuids) to the desired GUID's array size. The static analyzer indicates an error when output parameter
            from failed function is used, so the warning is suppressed */
#pragma warning(suppress : 6102)
            pguidDevClassPtr = new GUID[dwNumGuids];
        }
        else
        {
            NETCO_DEBUG_PRINT(TEXT("SetupDiClassGuidsFromNameEx failed with code ") << dwErr);
            return false;
        }

        if (!SetupDiClassGuidsFromNameEx(NETKVM_DEVICES_CLASS, pguidDevClassPtr, dwNumGuids, &dwNumGuids, NULL, NULL))
        {
            delete[] pguidDevClassPtr;
            NETCO_DEBUG_PRINT(TEXT("SetupDiClassGuidsFromNameEx failed with code ") << dwErr);
            return false;
        }

        GUIDs.insert(GUIDs.end(), &pguidDevClassPtr[0], &pguidDevClassPtr[dwNumGuids]);
        delete[] pguidDevClassPtr;
    }

    return true;
}

tstring _NetKVMQueryDeviceString(HDEVINFO hDeviceSet, PSP_DEVINFO_DATA DeviceInfoData, DWORD dwPropertyID)
{
    LPTSTR szDeviceString = NULL;
    DWORD dwSize = 0;
    DWORD dwDataType;

    while (!SetupDiGetDeviceRegistryProperty(hDeviceSet,
                                             DeviceInfoData,
                                             dwPropertyID,
                                             &dwDataType,
                                             (PBYTE)szDeviceString,
                                             dwSize,
                                             &dwSize))
    {
        DWORD dwErr = GetLastError();

        if (ERROR_INSUFFICIENT_BUFFER != dwErr)
        {
            delete[] szDeviceString;
            NETCO_DEBUG_PRINT(TEXT("SetupDiGetDeviceRegistryProperty failed with code ") << dwErr);
            return tstring();
        }
        /* According to SetupDiGetDeviceRegistryProperty, the RequiredSize output parameter (&dwSize) is set
          to the required size of PropertyBuffer (szDeviceString) parameter. The static analyzer indicates
          error when the output parameter from failed function, so the warning is suppressed */
#pragma warning(suppress : 6102)
        szDeviceString = new TCHAR[(dwSize / sizeof(TCHAR)) + 1];
    }
    if (REG_SZ != dwDataType)
    {
#pragma warning(suppress : 6102)
        delete[] szDeviceString;
        NETCO_DEBUG_PRINT(TEXT("SetupDiGetDeviceRegistryProperty(string) returned incorrect data type ") << dwDataType);
        return tstring();
    }

#pragma warning(suppress : 6102)
#pragma warning(suppress : 6011)
    szDeviceString[dwSize / sizeof(TCHAR)] = TEXT('\0');
    return tstring(szDeviceString);
}

DWORD _NetKVMQueryDeviceDWORD(HDEVINFO hDeviceSet, PSP_DEVINFO_DATA DeviceInfoData, DWORD dwPropertyID)
{
    DWORD dwDeviceDword;
    DWORD dwDataType;

    if (!SetupDiGetDeviceRegistryProperty(hDeviceSet,
                                          DeviceInfoData,
                                          dwPropertyID,
                                          &dwDataType,
                                          (PBYTE)&dwDeviceDword,
                                          sizeof(dwDeviceDword),
                                          NULL))
    {
        return 0;
    }
    if (REG_DWORD != dwDataType)
    {
        NETCO_DEBUG_PRINT(TEXT("SetupDiGetDeviceRegistryProperty(DWORD) returned incorrect data type ") << REG_DWORD);
        return 0;
    }
#pragma warning(suppress : 6102)
    return dwDeviceDword;
}

static HKEY _NetKVMOpenDeviceSwRegKey(HDEVINFO hDeviceSet, PSP_DEVINFO_DATA DeviceInfoData)
{
    HKEY hRes = SetupDiOpenDevRegKey(hDeviceSet, DeviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_READ);

    if (INVALID_HANDLE_VALUE == hRes)
    {
        PrintError(g_hinstThisDLL, IDS_CANNOTOPENKEY);
        tcout << TEXT("Error: ") << GetLastError() << endl;
    }

    return hRes;
}

typedef struct tag_NetKVMDeviceInfo
{
    tstring strDeviceID;
    tstring strDeviceDescription;
    tstring strDeviceFriendlyName;
    tstring strLocationInfo;
    tstring strRegPathName;
    DWORD dwDeviceNumber;
    SP_DEVINFO_DATA DevInfoData;
} _NetKVMDeviceInfo, *_PNetKVMDeviceInfo;

bool _NetKVMIsKnownDevice(const tstring &strDeviceID)
{
    return (tstring::npos != strDeviceID.find(tstring(TEXT("VEN_")) + NETKVM_DEVICE_VENDOR_ID)) &&
           (tstring::npos != strDeviceID.find(tstring(TEXT("DEV_")) + NETKVM_DEVICE_HARDWARE_ID));
}

tstring _NetKVMKeyPathFromDeviceSwHKEY(HKEY hKey)
{
    tstring strNativePath = wstring2tstring(NetKVMGetKeyPathFromKKEY(hKey));
    tstring strNativePrefix = TEXT("\\REGISTRY\\MACHINE\\");
    if (strNativePath.substr(0, strNativePrefix.length()) == strNativePrefix)
    {
        return strNativePath.substr(strNativePrefix.length());
    }
    else
    {
        return tstring();
    }
}

static bool _NetKVMGetKnownDevices(GUID *pguidDevClassPtr, vector<_NetKVMDeviceInfo> &Devices, HDEVINFO *phDeviceInfo)
{
    *phDeviceInfo = SetupDiGetClassDevsEx(pguidDevClassPtr, NULL, NULL, DIGCF_PRESENT, NULL, NULL, NULL);
    if (INVALID_HANDLE_VALUE == *phDeviceInfo)
    {
        DWORD dwErr = GetLastError();
        UNREFERENCED_PARAMETER(dwErr);
        NETCO_DEBUG_PRINT(TEXT("SetupDiGetClassDevsEx failed with code ") << dwErr);
        return false;
    }

    SP_DEVINFO_LIST_DETAIL_DATA DevListInfo;
    DevListInfo.cbSize = sizeof(DevListInfo);
    if (!SetupDiGetDeviceInfoListDetail(*phDeviceInfo, &DevListInfo))
    {
        DWORD dwErr = GetLastError();
        UNREFERENCED_PARAMETER(dwErr);
        NETCO_DEBUG_PRINT(TEXT("SetupDiGetDeviceInfoListDetail failed with code ") << dwErr);
        SetupDiDestroyDeviceInfoList(*phDeviceInfo);
        return false;
    }

    SP_DEVINFO_DATA CurrDeviceInfo;
    CurrDeviceInfo.cbSize = sizeof(CurrDeviceInfo);
    for (DWORD dwDevIndex = 0; SetupDiEnumDeviceInfo(*phDeviceInfo, dwDevIndex, &CurrDeviceInfo); dwDevIndex++)
    {
        TCHAR szDeviceId[MAX_DEVICE_ID_LEN];
        CONFIGRET err = CM_Get_Device_ID_Ex(CurrDeviceInfo.DevInst,
                                            szDeviceId,
                                            MAX_DEVICE_ID_LEN,
                                            0,
                                            DevListInfo.RemoteMachineHandle);
        if (CR_SUCCESS == err)
        {
            if (_NetKVMIsKnownDevice(szDeviceId))
            {
                _NetKVMDeviceInfo ResDevInfo;
                ResDevInfo.strDeviceID = szDeviceId;
                ResDevInfo.strDeviceDescription = _NetKVMQueryDeviceString(*phDeviceInfo,
                                                                           &CurrDeviceInfo,
                                                                           SPDRP_DEVICEDESC);
                ResDevInfo.strDeviceFriendlyName = _NetKVMQueryDeviceString(*phDeviceInfo,
                                                                            &CurrDeviceInfo,
                                                                            SPDRP_FRIENDLYNAME);
                if (ResDevInfo.strDeviceFriendlyName.length() == 0)
                {
                    ResDevInfo.strDeviceFriendlyName = ResDevInfo.strDeviceDescription;
                }
                ResDevInfo.strLocationInfo = _NetKVMQueryDeviceString(*phDeviceInfo,
                                                                      &CurrDeviceInfo,
                                                                      SPDRP_LOCATION_INFORMATION);
                ResDevInfo.dwDeviceNumber = _NetKVMQueryDeviceDWORD(*phDeviceInfo, &CurrDeviceInfo, SPDRP_UI_NUMBER);
                ResDevInfo.DevInfoData = CurrDeviceInfo;
                HKEY hSoftwareRegKey = _NetKVMOpenDeviceSwRegKey(*phDeviceInfo, &CurrDeviceInfo);
                if (INVALID_HANDLE_VALUE != hSoftwareRegKey)
                {
                    ResDevInfo.strRegPathName = _NetKVMKeyPathFromDeviceSwHKEY(hSoftwareRegKey);
                    CloseHandle(hSoftwareRegKey);
                    if (!ResDevInfo.strRegPathName.empty())
                    {
                        Devices.push_back(ResDevInfo);
                    }
                }
            }
        }
        else
        {
            NETCO_DEBUG_PRINT(TEXT("CM_Get_Device_ID_Ex failed with code ") << err);
            return false;
        }
    }

    DWORD dwErr = GetLastError();
    if (ERROR_NO_MORE_ITEMS != dwErr)
    {
        NETCO_DEBUG_PRINT(TEXT("SetupDiGetDeviceInfoListDetail failed with code ") << dwErr);
        SetupDiDestroyDeviceInfoList(*phDeviceInfo);
        return false;
    }

    return true;
}

static pair<HDEVINFO, vector<_NetKVMDeviceInfo>> _NetKVMGetDevicesOfInterest()
{
    vector<GUID> GUIDs;
    pair<HDEVINFO, vector<_NetKVMDeviceInfo>> Devices;

    _NetKVMGetDeviceClassGuids(GUIDs);
    if (GUIDs.empty())
    {
        return Devices;
    }

    _NetKVMGetKnownDevices(&GUIDs[0], Devices.second, &Devices.first);
    return Devices;
}

void _NetKVMDumpDeviceInfo(DWORD dwIndex, const _NetKVMDeviceInfo &DeviceInfo)
{
    PrintMessageFromModule(g_hinstThisDLL, IDS_DEVICEID);
    tcout << TEXT(": ") << DeviceInfo.strDeviceID << endl;

    tcout << TEXT("\t");
    PrintMessageFromModule(g_hinstThisDLL, IDS_DEVICEIDX);
    tcout << TEXT(": ") << dwIndex << endl;

    tcout << TEXT("\t");
    PrintMessageFromModule(g_hinstThisDLL, IDS_DEVICEDESC);
    tcout << TEXT(": ") << DeviceInfo.strDeviceDescription << endl;

    tcout << TEXT("\t");
    PrintMessageFromModule(g_hinstThisDLL, IDS_DEVICEFRNAME);
    tcout << TEXT(": ") << DeviceInfo.strDeviceFriendlyName << endl;

    tcout << TEXT("\t");
    PrintMessageFromModule(g_hinstThisDLL, IDS_DEVICELOCINFO);
    tcout << TEXT(": ") << DeviceInfo.strLocationInfo << endl;

    tcout << TEXT("\t");
    PrintMessageFromModule(g_hinstThisDLL, IDS_DEVICENUM);
    tcout << TEXT(": ") << DeviceInfo.dwDeviceNumber << endl;

    tcout << TEXT("\t");
    PrintMessageFromModule(g_hinstThisDLL, IDS_DEVICEREGKEY);
    tcout << TEXT(": ") << DeviceInfo.strRegPathName << endl << endl;
}

HDEVINFO g_hDeviceInfoList = INVALID_HANDLE_VALUE;
static vector<_NetKVMDeviceInfo> g_DevicesOfInterest;

static DWORD _NetKVMRestartDevice(DWORD dwIndex)
{
    SP_PROPCHANGE_PARAMS Params;
    Params.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
    Params.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
    Params.StateChange = DICS_PROPCHANGE;
    Params.Scope = DICS_FLAG_CONFIGSPECIFIC;
    Params.HwProfile = 0;

    if (!SetupDiSetClassInstallParams(g_hDeviceInfoList,
                                      &g_DevicesOfInterest[dwIndex].DevInfoData,
                                      &Params.ClassInstallHeader,
                                      sizeof(Params)))
    {
        return GetLastError();
    }

    if (!SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, g_hDeviceInfoList, &g_DevicesOfInterest[dwIndex].DevInfoData))
    {
        return GetLastError();
    }

    SP_DEVINSTALL_PARAMS DeviceInstallParams;
    DeviceInstallParams.cbSize = sizeof(SP_DEVINSTALL_PARAMS);

    if (!SetupDiGetDeviceInstallParams(g_hDeviceInfoList,
                                       &g_DevicesOfInterest[dwIndex].DevInfoData,
                                       &DeviceInstallParams))
    {
        return ERROR_SUCCESS_REBOOT_REQUIRED;
    }

    if (DeviceInstallParams.Flags & (DI_NEEDRESTART | DI_NEEDREBOOT))
    {
        return ERROR_SUCCESS_REBOOT_REQUIRED;
    }

    return NO_ERROR;
}

static DWORD __NetKVMwsz2DWORD(PWSTR wszInput)
{
    wstringstream wstrmConverter;
    wstrmConverter << wszInput;
    DWORD dwIndex;
    wstrmConverter >> dwIndex;
    return dwIndex;
}

static BOOL __NetKVMConvertDeviceIndex(PWSTR wszIndex, PDWORD pdwIndex)
{
    *pdwIndex = __NetKVMwsz2DWORD(wszIndex);
    if (*pdwIndex >= g_DevicesOfInterest.size())
    {
        PrintMessageFromModule(g_hinstThisDLL, IDS_INDEXOUTOFRANGE);
        tcout << endl;
        return FALSE;
    }
    else
    {
        return TRUE;
    }
}

typedef pair<tstring, tstring> ParamDescrT;
typedef list<ParamDescrT> ParamDescrListT;

static void _NetKVMFillDeviceParamsList(DWORD dwDeviceIndex, ParamDescrListT &List)
{
    auto_ptr<neTKVMRegParam> pParam;
    DWORD dwParamIndex = 0;
    neTKVMRegAccess DeviceRegKey(HKEY_LOCAL_MACHINE, g_DevicesOfInterest[dwDeviceIndex].strRegPathName.c_str());

    while (NULL != (pParam = auto_ptr<neTKVMRegParam>(neTKVMRegParam::GetParam(DeviceRegKey, dwParamIndex++))).get())
    {
        ParamDescrT ParamDescr;
        ParamDescr.first = pParam->GetName();
        ParamDescr.second = pParam->GetDescription();
        List.push_back(ParamDescr);
    }
}

typedef pair<tstring, tstring> ParamValueT;
typedef list<ParamValueT> ParamValueListT;

static void _NetKVMFillDeviceValuesList(DWORD dwDeviceIndex, ParamValueListT &List)
{
    auto_ptr<neTKVMRegParam> pParam;
    DWORD dwParamIndex = 0;
    neTKVMRegAccess DeviceRegKey(HKEY_LOCAL_MACHINE, g_DevicesOfInterest[dwDeviceIndex].strRegPathName.c_str());

    while (NULL != (pParam = auto_ptr<neTKVMRegParam>(neTKVMRegParam::GetParam(DeviceRegKey, dwParamIndex++))).get())
    {
        ParamValueT ParamValue;
        ParamValue.first = pParam->GetName();
        ParamValue.second = pParam->GetValue();
        List.push_back(ParamValue);
    }
}

static bool _NetKVMQueryDetailedParamInfo(DWORD dwDeviceIndex,
                                          const tstring &strParamName,
                                          neTKVMRegParamType &ParamType,
                                          neTKVMRegParamExInfoList &ParamInfo)
{
    neTKVMRegAccess DeviceRegKey(HKEY_LOCAL_MACHINE, g_DevicesOfInterest[dwDeviceIndex].strRegPathName.c_str());

    auto_ptr<neTKVMRegParam> pParam(neTKVMRegParam::GetParam(DeviceRegKey, strParamName.c_str()));

    ParamType = neTKVMRegParam::GetType(DeviceRegKey, strParamName.c_str());
    if (NETKVM_RTT_UNKNOWN == ParamType)
    {
        return false;
    }

    if (NULL == pParam.get())
    {
        return false;
    }

    pParam->FillExInfo(ParamInfo);
    return true;
}

static bool _NetKVMQueryParamValue(DWORD dwDeviceIndex, const tstring &strParamName, tstring &strParamValue)
{
    neTKVMRegAccess DeviceRegKey(HKEY_LOCAL_MACHINE, g_DevicesOfInterest[dwDeviceIndex].strRegPathName.c_str());

    auto_ptr<neTKVMRegParam> pParam(neTKVMRegParam::GetParam(DeviceRegKey, strParamName.c_str()));
    if (NULL == pParam.get())
    {
        return false;
    }

    strParamValue = pParam->GetValue();
    return true;
}

static bool _NetKVMSetParamValue(DWORD dwDeviceIndex, const tstring &strParamName, const tstring &strParamValue)
{
    neTKVMRegAccess DeviceRegKey(HKEY_LOCAL_MACHINE, g_DevicesOfInterest[dwDeviceIndex].strRegPathName.c_str());

    auto_ptr<neTKVMRegParam> pParam(neTKVMRegParam::GetParam(DeviceRegKey, strParamName.c_str()));
    if (NULL == pParam.get())
    {
        return false;
    }

    if (!pParam->ValidateAndSetValue(strParamValue.c_str()))
    {
        return false;
    }

    return pParam->Save();
}

//
// Usage: show devices
//
// Remarks:
//
//      Lists all NetKVM devices currently present in the system.
//      Each device is assigned unique index.
//      The index is used to identify the device for all other NetKVM commands.
//
DWORD WINAPI _NetKVMShowDevicesCmdHandler(__in PWCHAR /*pwszMachine*/,
                                          __in PWCHAR * /*ppwcArguments*/,
                                          __in DWORD /*dwCurrentIndex*/,
                                          __in DWORD /*dwArgCount*/,
                                          __in DWORD /*dwFlags*/,
                                          __in PVOID /*pvData*/,
                                          __out BOOL *pbDone)
{
    *pbDone = FALSE; /* Just to make static analyzer happy */

    try
    {
        NETCO_DEBUG_PRINT(TEXT("_NetKVMShowDevicesCmdHandler called"));
        int i = 0;
        for (vector<_NetKVMDeviceInfo>::const_iterator it = g_DevicesOfInterest.begin();
             it != g_DevicesOfInterest.end();
             ++it, ++i)
        {
            _NetKVMDumpDeviceInfo(i, *it);
        }
        return NO_ERROR;
    }
    catch (const exception &ex)
    {
        PrintError(g_hinstThisDLL, IDS_LOGICEXCEPTION);
        tcout << TEXT(": ") << string2tstring(string(ex.what())) << endl;
        return ERROR_EXCEPTION_IN_SERVICE;
    }
    catch (...)
    {
        return ERROR_UNKNOWN_EXCEPTION;
    }
}

//
// Usage: show paraminfo [idx=]0-N [param=]name
//
// Parameters:
//
//      IDX - Specifies the device index as it is shown in "show devices" output.
//      PARAM - Specifies name of the parameter.
//
// Remarks:
//
//      Shows detailed information about specified parameter of specified device.
//
// Examples:
//
//      show paraminfo idx=0 param=window
//      show paraminfo 2 rx_buffers
//
DWORD WINAPI _NetKVMShowParamInfoCmdHandler(__in PWCHAR /*pwszMachine*/,
                                            __in PWCHAR *ppwcArguments,
                                            __in DWORD dwCurrentIndex,
                                            __in DWORD dwArgCount,
                                            __in DWORD /*dwFlags*/,
                                            __in PVOID /*pvData*/,
                                            __out BOOL *pbDone)
{
    *pbDone = FALSE; /* Just to make static analyzer happy */

    try
    {
        NETCO_DEBUG_PRINT(TEXT("_NetKVMShowParamInfoCmdHandler called"));
        TAG_TYPE TagsList[] = {{NETKVM_IDX_PARAM_NAME, NS_REQ_PRESENT}, {NETKVM_PARAM_PARAM_NAME, NS_REQ_PRESENT}};

        auto_ptr<DWORD> pdwTagMatchResults(new DWORD[dwArgCount - dwCurrentIndex]);
        DWORD dwPreprocessResult = PreprocessCommand(NULL,
                                                     ppwcArguments,
                                                     dwCurrentIndex,
                                                     dwArgCount,
                                                     TagsList,
                                                     ARRAY_SIZE(TagsList),
                                                     ARRAY_SIZE(TagsList),
                                                     ARRAY_SIZE(TagsList),
                                                     pdwTagMatchResults.get());

        switch (dwPreprocessResult)
        {
            case NO_ERROR:
                {
                    DWORD dwIndex;
                    if (__NetKVMConvertDeviceIndex(ppwcArguments[dwCurrentIndex + pdwTagMatchResults.get()[0]],
                                                   &dwIndex))
                    {
                        wstring wstrParamName = ppwcArguments[dwCurrentIndex + pdwTagMatchResults.get()[1]];
                        neTKVMRegParamExInfoList ParamInfo;
                        neTKVMRegParamType ParamType;

                        if (!_NetKVMQueryDetailedParamInfo(dwIndex,
                                                           wstring2tstring(wstrParamName),
                                                           ParamType,
                                                           ParamInfo))
                        {
                            return ERROR_INVALID_PARAMETER;
                        }

                        PrintMessageFromModule(g_hinstThisDLL, IDS_PARAMTYPE);
                        tcout << TEXT(": ");
                        switch (ParamType)
                        {
                            case NETKVM_RTT_ENUM:
                                PrintMessageFromModule(g_hinstThisDLL, IDS_PARAMENUM);
                                break;
                            case NETKVM_RTT_INT:
                                PrintMessageFromModule(g_hinstThisDLL, IDS_PARAMINT);
                                break;
                            case NETKVM_RTT_LONG:
                                PrintMessageFromModule(g_hinstThisDLL, IDS_PARAMLONG);
                                break;
                            case NETKVM_RTT_EDIT:
                                PrintMessageFromModule(g_hinstThisDLL, IDS_PARAMTEXT);
                                break;
                            default:
                                PrintMessageFromModule(g_hinstThisDLL, IDS_PARAMUNKNOWN);
                                break;
                        }
                        tcout << endl;

                        for (neTKVMRegParamExInfoList::const_iterator it = ParamInfo.begin(); it != ParamInfo.end();
                             ++it)
                        {
                            switch (it->first)
                            {
                                case NETKVM_RPIID_ENUM_VALUE:
                                    tcout << TEXT("\t");
                                    PrintMessageFromModule(g_hinstThisDLL, IDS_PARAMENUMVALUE);
                                    tcout << TEXT(" \"") << it->second << TEXT("\" - ");
                                    break;
                                case NETKVM_RPIID_ENUM_VALUE_DESC:
                                    tcout << it->second << endl;
                                    break;
                                case NETKVM_RPIID_NUM_MIN:
                                    tcout << TEXT("\t");
                                    PrintMessageFromModule(g_hinstThisDLL, IDS_PARAMNUMMIN);
                                    tcout << TEXT(": ") << it->second << endl;
                                    break;
                                case NETKVM_RPIID_NUM_MAX:
                                    tcout << TEXT("\t");
                                    PrintMessageFromModule(g_hinstThisDLL, IDS_PARAMNUMMAX);
                                    tcout << TEXT(": ") << it->second << endl;
                                    break;
                                case NETKVM_RPIID_NUM_STEP:
                                    tcout << TEXT("\t");
                                    PrintMessageFromModule(g_hinstThisDLL, IDS_PARAMNUMSTEP);
                                    tcout << TEXT(": ") << it->second << endl;
                                    break;
                                case NETKVM_RPIID_EDIT_TEXT_LIMIT:
                                    tcout << TEXT("\t");
                                    PrintMessageFromModule(g_hinstThisDLL, IDS_PARAMTEXTLIMIT);
                                    tcout << TEXT(": ") << it->second << endl;
                                    break;
                                case NETKVM_RPIID_EDIT_UPPER_CASE:
                                    tcout << TEXT("\t");
                                    PrintMessageFromModule(g_hinstThisDLL, IDS_PARAMUPPERCASE);
                                    tcout << TEXT(": ") << it->second << endl;
                                    break;
                                default:
                                    tcout << TEXT("\t");
                                    PrintMessageFromModule(g_hinstThisDLL, IDS_PARAMUNKNOWNPROP);
                                    tcout << TEXT(": ") << it->second << endl;
                                    break;
                            }
                        }
                        return NO_ERROR;
                    }
                    else
                    {
                        return ERROR_INVALID_PARAMETER;
                    }
                }
                __fallthrough;
            default:
                NETCO_DEBUG_PRINT(TEXT("PreprocessCommand returned: ") << dwPreprocessResult);
                return dwPreprocessResult;
        }
    }
    catch (const exception &ex)
    {
        PrintError(g_hinstThisDLL, IDS_LOGICEXCEPTION);
        tcout << TEXT(": ") << string2tstring(string(ex.what())) << endl;
        return ERROR_EXCEPTION_IN_SERVICE;
    }
    catch (...)
    {
        return ERROR_UNKNOWN_EXCEPTION;
    }
}

//
// Usage: getparam [idx=]0-N [param=]name
//
// Parameters:
//
//      IDX - Specifies the device index as it is shown in "show devices" output.
//      PARAM - Specifies name of the parameter.
//
// Remarks:
//
//      Retrieves given parameter value.
//
// Examples:
//
//      getparam idx=0 param=window
//      getparam 2 rx_buffers
//
DWORD WINAPI _NetKVMGetParamCmdHandler(__in PWCHAR /*pwszMachine*/,
                                       __in PWCHAR *ppwcArguments,
                                       __in DWORD dwCurrentIndex,
                                       __in DWORD dwArgCount,
                                       __in DWORD /*dwFlags*/,
                                       __in PVOID /*pvData*/,
                                       __out BOOL *pbDone)
{
    *pbDone = FALSE; /* Just to make static analyzer happy */

    try
    {
        NETCO_DEBUG_PRINT(TEXT("_NetKVMGetParamCmdHandler called"));
        TAG_TYPE TagsList[] = {{NETKVM_IDX_PARAM_NAME, NS_REQ_PRESENT}, {NETKVM_PARAM_PARAM_NAME, NS_REQ_PRESENT}};

        auto_ptr<DWORD> pdwTagMatchResults(new DWORD[dwArgCount - dwCurrentIndex]);
        DWORD dwPreprocessResult = PreprocessCommand(NULL,
                                                     ppwcArguments,
                                                     dwCurrentIndex,
                                                     dwArgCount,
                                                     TagsList,
                                                     ARRAY_SIZE(TagsList),
                                                     ARRAY_SIZE(TagsList),
                                                     ARRAY_SIZE(TagsList),
                                                     pdwTagMatchResults.get());

        switch (dwPreprocessResult)
        {
            case NO_ERROR:
                {
                    DWORD dwIndex;
                    if (__NetKVMConvertDeviceIndex(ppwcArguments[dwCurrentIndex + pdwTagMatchResults.get()[0]],
                                                   &dwIndex))
                    {
                        wstring wstrParamName = ppwcArguments[dwCurrentIndex + pdwTagMatchResults.get()[1]];
                        tstring strParamName = wstring2tstring(wstrParamName);
                        tstring strParamValue;

                        if (!_NetKVMQueryParamValue(dwIndex, strParamName, strParamValue))
                        {
                            return ERROR_INVALID_PARAMETER;
                        }
                        tcout << strParamName << TEXT(" = ") << strParamValue << endl;
                        return NO_ERROR;
                    }
                    else
                    {
                        return ERROR_INVALID_PARAMETER;
                    }
                }
                __fallthrough;
            default:
                NETCO_DEBUG_PRINT(TEXT("PreprocessCommand returned: ") << dwPreprocessResult);
                return dwPreprocessResult;
        }
    }
    catch (const exception &ex)
    {
        PrintError(g_hinstThisDLL, IDS_LOGICEXCEPTION);
        tcout << TEXT(": ") << string2tstring(string(ex.what())) << endl;
        return ERROR_EXCEPTION_IN_SERVICE;
    }
    catch (...)
    {
        return ERROR_UNKNOWN_EXCEPTION;
    }
}

//
// Usage: setparam [idx=]0-N [param=]name [value=]value
//
// Parameters:
//
//      IDX - Specifies the device index as it is shown in "show devices" output.
//      PARAM - Specifies name of the parameter.
//      VALUE - Specifies the value of the parameter.
//
// Remarks:
//
//      Set given parameter value.
//
// Examples:
//
//      setparam idx=0 param=window value=10
//      setparam 2 rx_buffers 45
//
DWORD WINAPI _NetKVMSetParamCmdHandler(__in PWCHAR /*pwszMachine*/,
                                       __in PWCHAR *ppwcArguments,
                                       __in DWORD dwCurrentIndex,
                                       __in DWORD dwArgCount,
                                       __in DWORD /*dwFlags*/,
                                       __in PVOID /*pvData*/,
                                       __out BOOL *pbDone)
{
    *pbDone = FALSE; /* Just to make static analyzer happy */

    try
    {
        NETCO_DEBUG_PRINT(TEXT("_NetKVMSetParamCmdHandler called"));
        TAG_TYPE TagsList[] = {{NETKVM_IDX_PARAM_NAME, NS_REQ_PRESENT},
                               {NETKVM_PARAM_PARAM_NAME, NS_REQ_PRESENT},
                               {NETKVM_VALUE_PARAM_NAME, NS_REQ_PRESENT}};

        auto_ptr<DWORD> pdwTagMatchResults(new DWORD[dwArgCount - dwCurrentIndex]);
        DWORD dwPreprocessResult = PreprocessCommand(NULL,
                                                     ppwcArguments,
                                                     dwCurrentIndex,
                                                     dwArgCount,
                                                     TagsList,
                                                     ARRAY_SIZE(TagsList),
                                                     ARRAY_SIZE(TagsList),
                                                     ARRAY_SIZE(TagsList),
                                                     pdwTagMatchResults.get());

        switch (dwPreprocessResult)
        {
            case NO_ERROR:
                {
                    DWORD dwIndex;
                    if (__NetKVMConvertDeviceIndex(ppwcArguments[dwCurrentIndex + pdwTagMatchResults.get()[0]],
                                                   &dwIndex))
                    {
                        wstring wstrParamName = ppwcArguments[dwCurrentIndex + pdwTagMatchResults.get()[1]];
                        wstring wstrParamValue = ppwcArguments[dwCurrentIndex + pdwTagMatchResults.get()[2]];

                        if (!_NetKVMSetParamValue(dwIndex,
                                                  wstring2tstring(wstrParamName),
                                                  wstring2tstring(wstrParamValue)))
                        {
                            return ERROR_INVALID_PARAMETER;
                        }
                        return NO_ERROR;
                    }
                    else
                    {
                        return ERROR_INVALID_PARAMETER;
                    }
                }
                __fallthrough;
            default:
                NETCO_DEBUG_PRINT(TEXT("PreprocessCommand returned: ") << dwPreprocessResult);
                return dwPreprocessResult;
        }
    }
    catch (const exception &ex)
    {
        PrintError(g_hinstThisDLL, IDS_LOGICEXCEPTION);
        tcout << TEXT(": ") << string2tstring(string(ex.what())) << endl;
        return ERROR_EXCEPTION_IN_SERVICE;
    }
    catch (...)
    {
        return ERROR_UNKNOWN_EXCEPTION;
    }
}
//
// Usage: show parameters [idx=]0-N
//
// Parameters:
//
//      IDX - Specifies the device index as it is shown in "show devices" output.
//
// Remarks:
//
//      Shows parameters of device specified by index.
//
// Examples:
//
//      show parameters idx=0
//      show parameters 2
//
DWORD WINAPI _NetKVMShowParamsCmdHandler(__in PWCHAR /*pwszMachine*/,
                                         __in PWCHAR *ppwcArguments,
                                         __in DWORD dwCurrentIndex,
                                         __in DWORD dwArgCount,
                                         __in DWORD /*dwFlags*/,
                                         __in PVOID /*pvData*/,
                                         __out BOOL *pbDone)
{
    *pbDone = FALSE; /* Just to make static analyzer happy */

    try
    {
        NETCO_DEBUG_PRINT(TEXT("_NetKVMShowParamsCmdHandler called"));
        TAG_TYPE TagsList[] = {{NETKVM_IDX_PARAM_NAME, NS_REQ_PRESENT}};

        auto_ptr<DWORD> pdwTagMatchResults(new DWORD[dwArgCount - dwCurrentIndex]);
        DWORD dwPreprocessResult = PreprocessCommand(NULL,
                                                     ppwcArguments,
                                                     dwCurrentIndex,
                                                     dwArgCount,
                                                     TagsList,
                                                     ARRAY_SIZE(TagsList),
                                                     ARRAY_SIZE(TagsList),
                                                     ARRAY_SIZE(TagsList),
                                                     pdwTagMatchResults.get());

        switch (dwPreprocessResult)
        {
            case NO_ERROR:
                {
                    DWORD dwIndex;
                    if (__NetKVMConvertDeviceIndex(ppwcArguments[dwCurrentIndex + pdwTagMatchResults.get()[0]],
                                                   &dwIndex))
                    {
                        ParamDescrListT Params;

                        _NetKVMFillDeviceParamsList(dwIndex, Params);
                        for (ParamDescrListT::const_iterator it = Params.begin(); it != Params.end(); ++it)
                        {
                            tcout << it->first << endl << TEXT("\t") << it->second << endl;
                        }
                        return NO_ERROR;
                    }
                    else
                    {
                        return ERROR_INVALID_PARAMETER;
                    }
                }
                __fallthrough;
            default:
                NETCO_DEBUG_PRINT(TEXT("PreprocessCommand returned: ") << dwPreprocessResult);
                return dwPreprocessResult;
        }
    }
    catch (const exception &ex)
    {
        PrintError(g_hinstThisDLL, IDS_LOGICEXCEPTION);
        tcout << TEXT(": ") << string2tstring(string(ex.what())) << endl;
        return ERROR_EXCEPTION_IN_SERVICE;
    }
    catch (...)
    {
        return ERROR_UNKNOWN_EXCEPTION;
    }
}

//
// Usage: restart [idx=]0-N
//
// Parameters:
//
//      IDX - Specifies the device index as it is shown in "show devices" output.
//
// Remarks:
//
//      Restarts device specified by index.
//
// Examples:
//
//      restart idx=0
//      restart 2
//
DWORD WINAPI _NetKVMRestartDeviceCmdHandler(__in PWCHAR /*pwszMachine*/,
                                            __in PWCHAR *ppwcArguments,
                                            __in DWORD dwCurrentIndex,
                                            __in DWORD dwArgCount,
                                            __in DWORD /*dwFlags*/,
                                            __in PVOID /*pvData*/,
                                            __out BOOL *pbDone)
{
    *pbDone = FALSE; /* Just to make static analyzer happy */

    try
    {
        NETCO_DEBUG_PRINT(TEXT("_NetKVMRestartDeviceCmdHandler called"));
        TAG_TYPE TagsList[] = {{NETKVM_IDX_PARAM_NAME, NS_REQ_PRESENT}};

        auto_ptr<DWORD> pdwTagMatchResults(new DWORD[dwArgCount - dwCurrentIndex]);
        DWORD dwPreprocessResult = PreprocessCommand(NULL,
                                                     ppwcArguments,
                                                     dwCurrentIndex,
                                                     dwArgCount,
                                                     TagsList,
                                                     ARRAY_SIZE(TagsList),
                                                     ARRAY_SIZE(TagsList),
                                                     ARRAY_SIZE(TagsList),
                                                     pdwTagMatchResults.get());

        switch (dwPreprocessResult)
        {
            case NO_ERROR:
                {
                    DWORD dwIndex;
                    if (__NetKVMConvertDeviceIndex(ppwcArguments[dwCurrentIndex + pdwTagMatchResults.get()[0]],
                                                   &dwIndex))
                    {
                        PrintMessageFromModule(g_hinstThisDLL, IDS_RESTARTINGDEVICE);
                        tcout << TEXT(" ") << dwIndex << TEXT("... ");
                        DWORD dwError = _NetKVMRestartDevice(dwIndex);
                        switch (dwError)
                        {
                            case NO_ERROR:
                                PrintMessageFromModule(g_hinstThisDLL, IDS_DONE);
                                break;
                            case ERROR_SUCCESS_REBOOT_REQUIRED:
                                PrintMessageFromModule(g_hinstThisDLL, IDS_REBOOTREQUIRED);
                                break;
                            default:
                                PrintMessageFromModule(g_hinstThisDLL, IDS_FAIL);
                                break;
                        }
                        tcout << endl;
                        return dwError;
                    }
                    else
                    {
                        return ERROR_INVALID_PARAMETER;
                    }
                }
                __fallthrough;
            default:
                NETCO_DEBUG_PRINT(TEXT("PreprocessCommand returned: ") << dwPreprocessResult);
                return dwPreprocessResult;
        }
    }
    catch (const exception &ex)
    {
        PrintError(g_hinstThisDLL, IDS_LOGICEXCEPTION);
        tcout << TEXT(": ") << string2tstring(string(ex.what())) << endl;
        return ERROR_EXCEPTION_IN_SERVICE;
    }
    catch (...)
    {
        return ERROR_UNKNOWN_EXCEPTION;
    }
}

#define CMD_NETKVM_SHOW_DEVICES      L"devices"
#define HLP_NETKVM_SHOW_DEVICES      IDS_SHOWDEVICESSHORT
#define HLP_NETKVM_SHOW_DEVICES_EX   IDS_SHOWDEVICESLONG
#define CMD_NETKVM_SHOW_PARAMS       L"parameters"
#define HLP_NETKVM_SHOW_PARAMS       IDS_SHOWPARAMSSHORT
#define HLP_NETKVM_SHOW_PARAMS_EX    IDS_SHOWPARAMSLONG
#define CMD_NETKVM_SHOW_PARAMINFO    L"paraminfo"
#define HLP_NETKVM_SHOW_PARAMINFO    IDS_SHOWPARAMINFOSHORT
#define HLP_NETKVM_SHOW_PARAMINFO_EX IDS_SHOWPARAMINFOLONG

#define CREATE_CMD_ENTRY(t, f)                                                                                         \
    {                                                                                                                  \
        CMD_##t, f, HLP_##t, HLP_##t##_EX, CMD_FLAG_PRIVATE, NULL                                                      \
    }
#define CREATE_CMD_ENTRY_EX(t, f, i)                                                                                   \
    {                                                                                                                  \
        CMD_##t, f, HLP_##t, HLP_##t##_EX, i, NULL                                                                     \
    }
#define CREATE_CMD_ENTRY_EX_VER(t, f, i, v)                                                                            \
    {                                                                                                                  \
        CMD_##t, f, HLP_##t, HLP_##t##_EX, i, v                                                                        \
    }

#define CREATE_CMD_GROUP_ENTRY(t, s)                                                                                   \
    {                                                                                                                  \
        CMD_##t, HLP_##t, sizeof(s) / sizeof(CMD_ENTRY), 0, s, NULL                                                    \
    }
#define CREATE_CMD_GROUP_ENTRY_EX(t, s, i)                                                                             \
    {                                                                                                                  \
        CMD_##t, HLP_##t, sizeof(s) / sizeof(CMD_ENTRY), i, s, NULL                                                    \
    }
#define CREATE_CMD_GROUP_ENTRY_EX_VER(t, s, i, v)                                                                      \
    {                                                                                                                  \
        CMD_##t, HLP_##t, sizeof(s) / sizeof(CMD_ENTRY), i, s, v                                                       \
    }

#define CMD_FLAG_PRIVATE 0
#define CMD_FLAG_LOCAL   0

CMD_ENTRY g_ShowCmdTable[] = {CREATE_CMD_ENTRY_EX(NETKVM_SHOW_DEVICES,
                                                  (PFN_HANDLE_CMD)_NetKVMShowDevicesCmdHandler,
                                                  CMD_FLAG_PRIVATE | CMD_FLAG_LOCAL),
                              CREATE_CMD_ENTRY_EX(NETKVM_SHOW_PARAMS,
                                                  (PFN_HANDLE_CMD)_NetKVMShowParamsCmdHandler,
                                                  CMD_FLAG_PRIVATE | CMD_FLAG_LOCAL),
                              CREATE_CMD_ENTRY_EX(NETKVM_SHOW_PARAMINFO,
                                                  (PFN_HANDLE_CMD)_NetKVMShowParamInfoCmdHandler,
                                                  CMD_FLAG_PRIVATE | CMD_FLAG_LOCAL)

};

#define CMD_NETKVM_RESTART_DEVICE    L"restart"
#define CMD_NETKVM_RESTART_DEVICE_T  TEXT("restart")
#define HLP_NETKVM_RESTART_DEVICE    IDS_RESTARTDEVICE
#define HLP_NETKVM_RESTART_DEVICE_EX IDS_RESTARTDEVICELONG
#define CMD_NETKVM_GET_PARAM         L"getparam"
#define HLP_NETKVM_GET_PARAM         IDS_GETPARAM
#define HLP_NETKVM_GET_PARAM_EX      IDS_GETPARAMLONG
#define CMD_NETKVM_SET_PARAM         L"setparam"
#define CMD_NETKVM_SET_PARAM_T       TEXT("setparam")
#define HLP_NETKVM_SET_PARAM         IDS_SETPARAM
#define HLP_NETKVM_SET_PARAM_EX      IDS_SETPARAMLONG

CMD_ENTRY g_TopLevelCommands[] = {CREATE_CMD_ENTRY_EX(NETKVM_RESTART_DEVICE,
                                                      (PFN_HANDLE_CMD)_NetKVMRestartDeviceCmdHandler,
                                                      CMD_FLAG_PRIVATE | CMD_FLAG_LOCAL),
                                  CREATE_CMD_ENTRY_EX(NETKVM_GET_PARAM,
                                                      (PFN_HANDLE_CMD)_NetKVMGetParamCmdHandler,
                                                      CMD_FLAG_PRIVATE | CMD_FLAG_LOCAL),
                                  CREATE_CMD_ENTRY_EX(NETKVM_SET_PARAM,
                                                      (PFN_HANDLE_CMD)_NetKVMSetParamCmdHandler,
                                                      CMD_FLAG_PRIVATE | CMD_FLAG_LOCAL)};

#define HLP_GROUP_SHOW IDS_SHOWCMDHELP
#define CMD_GROUP_SHOW L"show"

static CMD_GROUP_ENTRY g_TopLevelGroups[] = {CREATE_CMD_GROUP_ENTRY_EX(GROUP_SHOW,
                                                                       g_ShowCmdTable,
                                                                       CMD_FLAG_PRIVATE | CMD_FLAG_LOCAL)};

DWORD WINAPI _NetKVMDumpCdmHandler(__in PWCHAR pwszRouter,
                                   __in WCHAR ** /* ppwcArguments */,
                                   __in DWORD /* dwArgCount */,
                                   __in PVOID /* pvData */)
{
    try
    {
        NETCO_DEBUG_PRINT(TEXT("_NetKVMDumpCdmHandler called"));

        if (NULL != pwszRouter)
        {
            PrintError(g_hinstThisDLL, IDS_LOCALONLY);
            return ERROR_INVALID_PARAMETER;
        }

        tcout << endl << TEXT("pushd ") << NETKVM_HELPER_NAME << endl << endl;

        for (DWORD i = 0; i < g_DevicesOfInterest.size(); i++)
        {
            ParamValueListT ValuesList;
            _NetKVMFillDeviceValuesList(i, ValuesList);

            for (ParamValueListT::const_iterator it = ValuesList.begin(); it != ValuesList.end(); ++it)
            {
                tcout << CMD_NETKVM_SET_PARAM_T << TEXT(" ") << NETKVM_IDX_PARAM_NAME_T << TEXT("=") << i << TEXT(" ")
                      << NETKVM_PARAM_PARAM_NAME_T << TEXT("=") << it->first << TEXT(" ") << NETKVM_VALUE_PARAM_NAME_T
                      << TEXT("=") << it->second << endl;
            }

            tcout << CMD_NETKVM_RESTART_DEVICE_T << TEXT(" ") << NETKVM_IDX_PARAM_NAME_T << TEXT("=") << i << TEXT(" ")
                  << endl
                  << endl;
        }

        tcout << TEXT("popd") << endl;

        return NO_ERROR;
    }
    catch (const exception &ex)
    {
        PrintError(g_hinstThisDLL, IDS_LOGICEXCEPTION);
        tcout << TEXT(": ") << string2tstring(string(ex.what())) << endl;
        return ERROR_EXCEPTION_IN_SERVICE;
    }
    catch (...)
    {
        return ERROR_UNKNOWN_EXCEPTION;
    }
}

DWORD WINAPI NetKVMNetshStartHelper(__in const GUID *pguidParent, __in DWORD dwVersion)
{
    try
    {
        UNREFERENCED_PARAMETER(pguidParent);
        UNREFERENCED_PARAMETER(dwVersion);

        NETCO_DEBUG_PRINT(TEXT("NetKVMNetshStartHelper called"));

        pair<HDEVINFO, vector<_NetKVMDeviceInfo>> Devices = _NetKVMGetDevicesOfInterest();
        g_hDeviceInfoList = Devices.first;
        g_DevicesOfInterest = Devices.second;

        if (g_DevicesOfInterest.empty())
        {
            PrintMessageFromModule(g_hinstThisDLL, IDS_NODEVICESFOUND);
            return NO_ERROR;
        }

        NS_CONTEXT_ATTRIBUTES attr;
        ZeroMemory(&attr, sizeof(attr));
        attr.dwVersion = NETKVM_HELPER_VERSION;
        attr.dwReserved = 0;
        attr.pwszContext = NETKVM_HELPER_NAME_W;
        attr.guidHelper = NETKVM_HELPER_GUID;
        attr.dwFlags = CMD_FLAG_LOCAL;
        attr.ulPriority = 0;
        attr.ulNumTopCmds = ARRAYSIZE(g_TopLevelCommands);
        attr.pTopCmds = (CMD_ENTRY(*)[])g_TopLevelCommands;
        attr.ulNumGroups = ARRAYSIZE(g_TopLevelGroups);
        attr.pCmdGroups = (CMD_GROUP_ENTRY(*)[])g_TopLevelGroups;
        attr.pfnDumpFn = (PNS_CONTEXT_DUMP_FN)_NetKVMDumpCdmHandler;
        attr.pReserved = NULL;
        RegisterContext(&attr);

        NETCO_DEBUG_PRINT(TEXT("RegisterContext returned"));
    }
    catch (const exception &ex)
    {
        PrintError(g_hinstThisDLL, IDS_LOGICEXCEPTION);
        tcout << TEXT(": ") << string2tstring(string(ex.what())) << endl;
        return ERROR_EXCEPTION_IN_SERVICE;
    }
    catch (...)
    {
        NETCO_DEBUG_PRINT(TEXT("Unknown exception"));
        return ERROR_UNKNOWN_EXCEPTION;
    }

    return NO_ERROR;
}

DWORD WINAPI NetKVMNetshStopHelper(__in DWORD dwReserved)
{
    UNREFERENCED_PARAMETER(dwReserved);

    NETCO_DEBUG_PRINT(TEXT("NetKVMNetshStopHelper called"));

    return SetupDiDestroyDeviceInfoList(g_hDeviceInfoList) ? NO_ERROR : GetLastError();
}
